/*
 * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.awt.event;

import sun.awt.AWTAccessor;

import java.awt.ActiveEvent;
import java.awt.AWTEvent;

/**
 * An event which executes the <code>run()</code> method on a <code>Runnable
 * </code> when dispatched by the AWT event dispatcher thread. This class can
 * be used as a reference implementation of <code>ActiveEvent</code> rather
 * than declaring a new class and defining <code>dispatch()</code>.<p>
 *
 * Instances of this class are placed on the <code>EventQueue</code> by calls
 * to <code>invokeLater</code> and <code>invokeAndWait</code>. Client code
 * can use this fact to write replacement functions for <code>invokeLater
 * </code> and <code>invokeAndWait</code> without writing special-case code
 * in any <code>AWTEventListener</code> objects.
 * <p>
 * An unspecified behavior will be caused if the {@code id} parameter
 * of any particular {@code InvocationEvent} instance is not
 * in the range from {@code INVOCATION_FIRST} to {@code INVOCATION_LAST}.
 *
 * @author Fred Ecks
 * @author David Mendenhall
 * @see java.awt.ActiveEvent
 * @see java.awt.EventQueue#invokeLater
 * @see java.awt.EventQueue#invokeAndWait
 * @see AWTEventListener
 * @since 1.2
 */
public class InvocationEvent extends AWTEvent implements ActiveEvent {

  static {
    AWTAccessor.setInvocationEventAccessor(new AWTAccessor.InvocationEventAccessor() {
      @Override
      public void dispose(InvocationEvent invocationEvent) {
        invocationEvent.finishedDispatching(false);
      }
    });
  }

  /**
   * Marks the first integer id for the range of invocation event ids.
   */
  public static final int INVOCATION_FIRST = 1200;

  /**
   * The default id for all InvocationEvents.
   */
  public static final int INVOCATION_DEFAULT = INVOCATION_FIRST;

  /**
   * Marks the last integer id for the range of invocation event ids.
   */
  public static final int INVOCATION_LAST = INVOCATION_DEFAULT;

  /**
   * The Runnable whose run() method will be called.
   */
  protected Runnable runnable;

  /**
   * The (potentially null) Object whose notifyAll() method will be called
   * immediately after the Runnable.run() method has returned or thrown an exception
   * or after the event was disposed.
   *
   * @see #isDispatched
   */
  protected volatile Object notifier;

  /**
   * The (potentially null) Runnable whose run() method will be called
   * immediately after the event was dispatched or disposed.
   *
   * @see #isDispatched
   * @since 1.8
   */
  private final Runnable listener;

  /**
   * Indicates whether the <code>run()</code> method of the <code>runnable</code>
   * was executed or not.
   *
   * @see #isDispatched
   * @since 1.7
   */
  private volatile boolean dispatched = false;

  /**
   * Set to true if dispatch() catches Throwable and stores it in the
   * exception instance variable. If false, Throwables are propagated up
   * to the EventDispatchThread's dispatch loop.
   */
  protected boolean catchExceptions;

  /**
   * The (potentially null) Exception thrown during execution of the
   * Runnable.run() method. This variable will also be null if a particular
   * instance does not catch exceptions.
   */
  private Exception exception = null;

  /**
   * The (potentially null) Throwable thrown during execution of the
   * Runnable.run() method. This variable will also be null if a particular
   * instance does not catch exceptions.
   */
  private Throwable throwable = null;

  /**
   * The timestamp of when this event occurred.
   *
   * @serial
   * @see #getWhen
   */
  private long when;

  /*
   * JDK 1.1 serialVersionUID.
   */
  private static final long serialVersionUID = 436056344909459450L;

  /**
   * Constructs an <code>InvocationEvent</code> with the specified source which will execute the
   * runnable's <code>run</code> method when dispatched. <p>This is a convenience constructor.  An
   * invocation of the form <tt>InvocationEvent(source, runnable)</tt> behaves in exactly the same
   * way as the invocation of <tt>{@link #InvocationEvent(Object, Runnable, Object, boolean)
   * InvocationEvent}(source, runnable, null, false)</tt>. <p> This method throws an
   * <code>IllegalArgumentException</code> if <code>source</code> is <code>null</code>.
   *
   * @param source The <code>Object</code> that originated the event
   * @param runnable The <code>Runnable</code> whose <code>run</code> method will be executed
   * @throws IllegalArgumentException if <code>source</code> is null
   * @see #getSource()
   * @see #InvocationEvent(Object, Runnable, Object, boolean)
   */
  public InvocationEvent(Object source, Runnable runnable) {
    this(source, INVOCATION_DEFAULT, runnable, null, null, false);
  }

  /**
   * Constructs an <code>InvocationEvent</code> with the specified source which will execute the
   * runnable's <code>run</code> method when dispatched.  If notifier is non-<code>null</code>,
   * <code>notifyAll()</code> will be called on it immediately after <code>run</code> has returned
   * or thrown an exception. <p>An invocation of the form <tt>InvocationEvent(source, runnable,
   * notifier, catchThrowables)</tt> behaves in exactly the same way as the invocation of <tt>{@link
   * #InvocationEvent(Object, int, Runnable, Object, boolean) InvocationEvent}(source,
   * InvocationEvent.INVOCATION_DEFAULT, runnable, notifier, catchThrowables)</tt>. <p>This method
   * throws an <code>IllegalArgumentException</code> if <code>source</code> is <code>null</code>.
   *
   * @param source The <code>Object</code> that originated the event
   * @param runnable The <code>Runnable</code> whose <code>run</code> method will be executed
   * @param notifier The {@code Object} whose <code>notifyAll</code> method will be called after
   * <code>Runnable.run</code> has returned or thrown an exception or after the event was disposed
   * @param catchThrowables Specifies whether <code>dispatch</code> should catch Throwable when
   * executing the <code>Runnable</code>'s <code>run</code> method, or should instead propagate
   * those Throwables to the EventDispatchThread's dispatch loop
   * @throws IllegalArgumentException if <code>source</code> is null
   * @see #getSource()
   * @see #InvocationEvent(Object, int, Runnable, Object, boolean)
   */
  public InvocationEvent(Object source, Runnable runnable, Object notifier,
      boolean catchThrowables) {
    this(source, INVOCATION_DEFAULT, runnable, notifier, null, catchThrowables);
  }

  /**
   * Constructs an <code>InvocationEvent</code> with the specified
   * source which will execute the runnable's <code>run</code>
   * method when dispatched.  If listener is non-<code>null</code>,
   * <code>listener.run()</code> will be called immediately after
   * <code>run</code> has returned, thrown an exception or the event
   * was disposed.
   * <p>This method throws an <code>IllegalArgumentException</code>
   * if <code>source</code> is <code>null</code>.
   *
   * @param source The <code>Object</code> that originated the event
   * @param runnable The <code>Runnable</code> whose <code>run</code> method will be executed
   * @param listener The <code>Runnable</code>Runnable whose <code>run()</code> method will be
   * called after the {@code InvocationEvent} was dispatched or disposed
   * @param catchThrowables Specifies whether <code>dispatch</code> should catch Throwable when
   * executing the <code>Runnable</code>'s <code>run</code> method, or should instead propagate
   * those Throwables to the EventDispatchThread's dispatch loop
   * @throws IllegalArgumentException if <code>source</code> is null
   */
  public InvocationEvent(Object source, Runnable runnable, Runnable listener,
      boolean catchThrowables) {
    this(source, INVOCATION_DEFAULT, runnable, null, listener, catchThrowables);
  }

  /**
   * Constructs an <code>InvocationEvent</code> with the specified
   * source and ID which will execute the runnable's <code>run</code>
   * method when dispatched.  If notifier is non-<code>null</code>,
   * <code>notifyAll</code> will be called on it immediately after
   * <code>run</code> has returned or thrown an exception.
   * <p>This method throws an
   * <code>IllegalArgumentException</code> if <code>source</code>
   * is <code>null</code>.
   *
   * @param source The <code>Object</code> that originated the event
   * @param id An integer indicating the type of event. For information on allowable values, see the
   * class description for {@link InvocationEvent}
   * @param runnable The <code>Runnable</code> whose <code>run</code> method will be executed
   * @param notifier The <code>Object</code> whose <code>notifyAll</code> method will be called
   * after <code>Runnable.run</code> has returned or thrown an exception or after the event was
   * disposed
   * @param catchThrowables Specifies whether <code>dispatch</code> should catch Throwable when
   * executing the <code>Runnable</code>'s <code>run</code> method, or should instead propagate
   * those Throwables to the EventDispatchThread's dispatch loop
   * @throws IllegalArgumentException if <code>source</code> is null
   * @see #getSource()
   * @see #getID()
   */
  protected InvocationEvent(Object source, int id, Runnable runnable,
      Object notifier, boolean catchThrowables) {
    this(source, id, runnable, notifier, null, catchThrowables);
  }

  private InvocationEvent(Object source, int id, Runnable runnable,
      Object notifier, Runnable listener, boolean catchThrowables) {
    super(source, id);
    this.runnable = runnable;
    this.notifier = notifier;
    this.listener = listener;
    this.catchExceptions = catchThrowables;
    this.when = System.currentTimeMillis();
  }

  /**
   * Executes the Runnable's <code>run()</code> method and notifies the
   * notifier (if any) when <code>run()</code> has returned or thrown an exception.
   *
   * @see #isDispatched
   */
  public void dispatch() {
    try {
      if (catchExceptions) {
        try {
          runnable.run();
        } catch (Throwable t) {
          if (t instanceof Exception) {
            exception = (Exception) t;
          }
          throwable = t;
        }
      } else {
        runnable.run();
      }
    } finally {
      finishedDispatching(true);
    }
  }

  /**
   * Returns any Exception caught while executing the Runnable's <code>run()
   * </code> method.
   *
   * @return A reference to the Exception if one was thrown; null if no Exception was thrown or if
   * this InvocationEvent does not catch exceptions
   */
  public Exception getException() {
    return (catchExceptions) ? exception : null;
  }

  /**
   * Returns any Throwable caught while executing the Runnable's <code>run()
   * </code> method.
   *
   * @return A reference to the Throwable if one was thrown; null if no Throwable was thrown or if
   * this InvocationEvent does not catch Throwables
   * @since 1.5
   */
  public Throwable getThrowable() {
    return (catchExceptions) ? throwable : null;
  }

  /**
   * Returns the timestamp of when this event occurred.
   *
   * @return this event's timestamp
   * @since 1.4
   */
  public long getWhen() {
    return when;
  }

  /**
   * Returns {@code true} if the event is dispatched or any exception is
   * thrown while dispatching, {@code false} otherwise. The method should
   * be called by a waiting thread that calls the {@code notifier.wait()} method.
   * Since spurious wakeups are possible (as explained in {@link Object#wait()}),
   * this method should be used in a waiting loop to ensure that the event
   * got dispatched:
   * <pre>
   *     while (!event.isDispatched()) {
   *         notifier.wait();
   *     }
   * </pre>
   * If the waiting thread wakes up without dispatching the event,
   * the {@code isDispatched()} method returns {@code false}, and
   * the {@code while} loop executes once more, thus, causing
   * the awakened thread to revert to the waiting mode.
   * <p>
   * If the {@code notifier.notifyAll()} happens before the waiting thread
   * enters the {@code notifier.wait()} method, the {@code while} loop ensures
   * that the waiting thread will not enter the {@code notifier.wait()} method.
   * Otherwise, there is no guarantee that the waiting thread will ever be woken
   * from the wait.
   *
   * @return {@code true} if the event has been dispatched, or any exception has been thrown while
   * dispatching, {@code false} otherwise
   * @see #dispatch
   * @see #notifier
   * @see #catchExceptions
   * @since 1.7
   */
  public boolean isDispatched() {
    return dispatched;
  }

  /**
   * Called when the event was dispatched or disposed
   *
   * @param dispatched true if the event was dispatched false if the event was disposed
   */
  private void finishedDispatching(boolean dispatched) {
    this.dispatched = dispatched;

    if (notifier != null) {
      synchronized (notifier) {
        notifier.notifyAll();
      }
    }

    if (listener != null) {
      listener.run();
    }
  }

  /**
   * Returns a parameter string identifying this event.
   * This method is useful for event-logging and for debugging.
   *
   * @return A string identifying the event and its attributes
   */
  public String paramString() {
    String typeStr;
    switch (id) {
      case INVOCATION_DEFAULT:
        typeStr = "INVOCATION_DEFAULT";
        break;
      default:
        typeStr = "unknown type";
    }
    return typeStr + ",runnable=" + runnable + ",notifier=" + notifier +
        ",catchExceptions=" + catchExceptions + ",when=" + when;
  }
}
